package com.msgc.utils.crypto.negotiation.service.impl;

import com.msgc.utils.crypto.asymmetric.exception.AsymmetricCryptoException;
import com.msgc.utils.crypto.common.ByteSpecification;
import com.msgc.utils.crypto.negotiation.cache.KeyNegotiationCache;
import com.msgc.utils.crypto.negotiation.cache.dto.KeyExchangeResult;
import com.msgc.utils.crypto.negotiation.constant.KeyExchangeConstants;
import com.msgc.utils.crypto.negotiation.dto.KeyExchangeRequest;
import com.msgc.utils.crypto.negotiation.dto.KeyExchangeResponse;
import com.msgc.utils.crypto.negotiation.exception.NegotiationException;
import com.msgc.utils.crypto.negotiation.service.TransportNegotiationService;
import com.msgc.utils.crypto.negotiation.util.TransportCryptoUtilAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author Admin
 */
@Slf4j
@Component
public class TransportNegotiationServiceImpl implements TransportNegotiationService {

    @Autowired
    TransportCryptoUtilAdapter transportCryptoUtil;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    KeyNegotiationCache keyNegotiationCache;

    // ===============================  请求协商密钥  ==============================


    @Override
    public KeyExchangeResult requestForNegotiate(String serverUrl) throws NegotiationException {
        try {
            // 1. 先尝试走缓存
            KeyExchangeResult cacheResult = keyNegotiationCache.getAsClient(serverUrl);
            if(cacheResult != null){
                return cacheResult;
            }

            // 2. 缓存不存在，发请求
            ResponseEntity<KeyExchangeResponse> httpResponse = restTemplate.postForEntity(serverUrl, createKeyNegotiationHttpEntity(), KeyExchangeResponse.class);

            // 3. 校验密钥协商的结果
            KeyExchangeResponse keyExchangeResponse = validateAndFill(httpResponse);

            // 4. 交换密钥
            KeyExchangeResult result = transportCryptoUtil.negotiation(keyExchangeResponse);

            // 5. 放缓存
            keyNegotiationCache.putAsClient(serverUrl, result);
            return result;
        } catch (Exception e) {
            log.error("negotiate Fail, url is " + serverUrl, e);
            throw new NegotiationException("negotiate Fail, url is " + serverUrl, e);
        }
    }

    /**
     * 创建请求体
     */
    private HttpEntity<KeyExchangeRequest> createKeyNegotiationHttpEntity() throws AsymmetricCryptoException {
        // 1. 创建 body
        KeyExchangeRequest requestParam = transportCryptoUtil.createRequest();

        // 2. 处理请求头，如 token 相关
        String xSessionId = requestParam.getxSessionId();
        String token = requestParam.getToken();

        HttpHeaders httpHeaders = new HttpHeaders();
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        httpHeaders.setAccept(mediaTypes);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
        httpHeaders.add(KeyExchangeConstants.SECURITY_SESSION_ID, xSessionId);
        httpHeaders.add(KeyExchangeConstants.TOKEN, token);

        return new HttpEntity<>(requestParam, httpHeaders);
    }

    /**
     * 校验响应是否合法，token 部分
     * 并将 xSessionId、token 从请求头中放到返回值中
     *
     * @param httpResponse 握手响应
     * @return 合法的响应
     */
    private KeyExchangeResponse validateAndFill(ResponseEntity<KeyExchangeResponse> httpResponse) throws AsymmetricCryptoException, NegotiationException {
        KeyExchangeResponse response = httpResponse.getBody();
        if (HttpStatus.OK != httpResponse.getStatusCode() || response == null) {
            throw new NegotiationException("response error! response = " + (response == null ? "null" : response.toString()));
        }
        String token = httpResponse.getHeaders().getFirst(KeyExchangeConstants.TOKEN);
        String xSessionId = httpResponse.getHeaders().getFirst(KeyExchangeConstants.SECURITY_SESSION_ID);

        response.setxSessionId(xSessionId);
        response.setToken(token);

        if (!transportCryptoUtil.verifyResponseToken(response)) {
            throw new NegotiationException("token not validate!");
        }

        return response;
    }


    // ===============================  处理请求  ==============================

    @Override
    public KeyExchangeResponse handleNegotiate(KeyExchangeRequest keyExchangeRequest) throws NegotiationException {
        KeyExchangeResult keyExchangeResult = null;
        // 校验
        try {
            validateAndFill(keyExchangeRequest);
            if(!keyExchangeRequest.isRefresh()){
                //不强制刷新 尝试走缓存
                keyExchangeResult = keyNegotiationCache.getAsServer(keyExchangeRequest.getxSessionId());
                if(keyExchangeResult != null){
                    return generateResponse(keyExchangeResult);
                }
            }

            // 交换密钥
            KeyExchangeResponse response = transportCryptoUtil.negotiation(keyExchangeRequest);

            // 放缓存
            KeyExchangeResult result = transportCryptoUtil.negotiation(response);
            keyNegotiationCache.putAsServer(response.getxSessionId(), result);

            // processHeaders
            HttpServletResponse httpResponse = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
            httpResponse.setHeader(KeyExchangeConstants.SECURITY_SESSION_ID, response.getxSessionId());
            httpResponse.setHeader(KeyExchangeConstants.TOKEN, response.getToken());

            return response;
        } catch (Exception e) {
            throw new NegotiationException("recieve request, negotiate Fail!", e);
        }
    }

    /**
     * 根据缓存内容生成握手响应
     */
    private KeyExchangeResponse generateResponse(KeyExchangeResult keyExchangeResult) throws AsymmetricCryptoException {
        KeyExchangeResponse response = new KeyExchangeResponse();

        byte[] publicKey = keyExchangeResult.getPublicKey();

        response.setPublicKey(ByteSpecification.encodeToString(publicKey));
        response.setExpireTime((int) (keyExchangeResult.getExpireTime() - System.currentTimeMillis()));
        response.setKeyLength(keyExchangeResult.getKeyLength());
        response.setAes("256");

        response.setxSessionId(keyExchangeResult.getxSessionId());
        String token = transportCryptoUtil.generateResponseToken(response);
        response.setToken(token);
        return response;
    }

    /**
     * 校验请求是否合法，token 部分
     * 并将 xSessionId、token 从请求头中放到返回值中
     */
    private KeyExchangeRequest validateAndFill(@NonNull KeyExchangeRequest keyExchangeRequest) throws AsymmetricCryptoException, NegotiationException {

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        String xSessionId = request.getHeader(KeyExchangeConstants.SECURITY_SESSION_ID);
        String token = request.getHeader(KeyExchangeConstants.TOKEN);

        keyExchangeRequest.setxSessionId(xSessionId);
        keyExchangeRequest.setToken(token);

        if (!transportCryptoUtil.verifyRequestToken(keyExchangeRequest)) {
            throw new NegotiationException("token not validate!");
        }
        return keyExchangeRequest;
    }

}
